跳到主要内容

MySQL 中的行锁

面试题1:MySQL 行锁的基本原理

问题: 请解释 MySQL 中行锁的实现原理,以及 InnoDB 和 MyISAM 在锁机制上的区别?

参考答案:

  • MySQL 的行锁是在引擎层实现的,MyISAM 不支持行锁只支持表锁,InnoDB 支持行锁
  • InnoDB 行锁是通过给索引上的索引项加锁来实现的,不是对数据行本身加锁
  • 只有通过索引条件检索数据时,InnoDB 才使用行级锁,否则使用表锁
  • 即使条件中使用了索引字段,如果 MySQL 认为全表扫描更高效,仍会使用表锁

面试题2:两阶段锁协议的应用场景

问题: 什么是两阶段锁协议?在实际业务中如何利用这个特性来优化并发性能?

参考答案:

  • 两阶段锁协议:行锁在需要时加上,但要等到事务结束时才释放
  • 优化策略:把最可能造成锁冲突、最可能影响并发度的锁尽量往后放

业务场景示例: 电影票交易系统

解释说明:

  • 优化前:影院账户行锁持有时间较长,阻塞其他事务
  • 优化后:将最容易产生冲突的操作放在最后,减少锁等待时间

面试题3:死锁检测与解决方案

问题: 请描述 MySQL 中死锁的产生原因和检测机制,以及在高并发场景下如何优化死锁检测的性能问题?

死锁产生示例:

参考答案:

死锁检测机制:

  • innodb_lock_wait_timeout:超时等待策略(默认50s)
  • innodb_deadlock_detect:主动死锁检测(默认开启)

性能优化方案:

  1. 控制并发度:限制同时访问相同资源的线程数
  2. 业务层面优化:将热点行拆分成多行
    -- 原来:一个影院账户
    UPDATE cinema_account SET balance = balance + 100 WHERE cinema_id = 1;

    -- 优化:拆分成10个子账户
    UPDATE cinema_account SET balance = balance + 100
    WHERE cinema_id = 1 AND sub_account = FLOOR(RAND() * 10);

面试题4:共享锁与排他锁的兼容性

问题: 请说明共享锁(S锁)和排他锁(X锁)的使用场景和兼容性规则?

兼容性矩阵:

使用示例:

-- 共享锁示例
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁示例
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET status = 'paid' WHERE id = 1; -- 自动加X锁

面试题5:行锁的三种算法详解

问题: InnoDB 中有哪三种行锁算法?它们分别在什么场景下使用?

Record Lock (记录锁)

使用场景: 唯一索引的等值查询

SELECT * FROM t WHERE id = 4 FOR UPDATE; -- id是主键

Gap Lock (间隙锁)

使用场景: 防止幻读,锁定记录间的间隙

SELECT * FROM t WHERE id = 7 FOR UPDATE; -- id=7不存在,锁定(5,10)间隙

Next-Key Lock (临键锁)

使用场景: 范围查询的默认锁算法

SELECT * FROM t WHERE id >= 10 AND id <= 15 FOR UPDATE;

面试题6:加锁规则的实际应用

问题: 请根据 InnoDB 的加锁规则,分析以下 SQL 的加锁情况?

表结构:

CREATE TABLE t (
id int(11) NOT NULL,
c int(11) DEFAULT NULL,
d int(11) DEFAULT NULL,
PRIMARY KEY (id),
KEY c (c)
);

-- 数据:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25)

案例分析:

案例1:等值查询不存在的记录

SELECT * FROM t WHERE id = 7 FOR UPDATE;

分析过程:

结果: 加间隙锁 (5,10),允许修改id=10的记录,但不允许插入id=8的记录。

案例2:非唯一索引等值查询

SELECT d FROM t WHERE c = 5 LOCK IN SHARE MODE;

分析过程:

结果:

  • c索引上加锁:(0,5] + (5,10)
  • 主键索引无锁(覆盖索引优化)

面试题7:锁升级和锁优化

问题: 在什么情况下会发生锁升级?如何通过索引设计来优化锁的粒度?

参考答案:

锁升级场景

  1. 无索引查询:使用表锁而非行锁
  2. 索引失效:MySQL选择全表扫描时使用表锁
  3. 大范围查询:锁定行数过多时可能升级

优化策略

-- 不良示例:无索引,使用表锁
UPDATE user SET status = 1 WHERE name = 'John';

-- 优化:添加索引,使用行锁
ALTER TABLE user ADD INDEX idx_name (name);
UPDATE user SET status = 1 WHERE name = 'John';

-- 进一步优化:复合索引
ALTER TABLE user ADD INDEX idx_name_status (name, status);

面试题8:隔离级别对锁的影响

问题: 不同事务隔离级别下,锁的行为有什么区别?

锁行为对比:

实际差异:

-- RR级别:会产生间隙锁
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM t WHERE id = 7 FOR UPDATE; -- 锁住(5,10)间隙

-- RC级别:不会产生间隙锁
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM t WHERE id = 7 FOR UPDATE; -- 无锁(记录不存在)

面试题9:锁等待和超时处理

问题: 在生产环境中如何监控和处理锁等待问题?

监控手段:

-- 查看当前锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看事务状态
SELECT * FROM information_schema.INNODB_TRX;

-- 查看锁等待超时设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

处理策略:

  1. 优化SQL:确保使用正确索引
  2. 调整超时时间:根据业务需求设置合理的超时值
  3. 事务拆分:减少长事务的锁持有时间
  4. 死锁重试:应用层实现死锁重试机制

面试题10:实际生产问题诊断

问题: 如果生产环境出现"CPU使用率很高但TPS很低"的问题,你会如何排查?

排查流程:

具体解决方案:

  1. 临时方案:关闭死锁检测(innodb_deadlock_detect=OFF)
  2. 根本方案:热点行拆分、并发控制、索引优化
  3. 监控方案:建立锁等待监控告警机制

Reference